﻿using System;
using System.IO;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using System.Windows.Forms;
using System.Text.RegularExpressions;
using SFP.Animation2D;

namespace ParticleSystemCreator
{
    public partial class ControlForm : Form
    {
        ParticleScene particleScene;
        
        PSPreset preset;

        // Emitter Prefs
        double amountPerSec = 400;
        double velocity = 40;
        double velocityRan = 0;
        double direction = 0;
        double directionRan = 100;

        // Particle Prefs
        int particleType = ParticleSystem.PARTICLE_CIRCLE;
        string particleImgPath = "";
        double liveTime = 2;
        double liveTimeRan = 10;
        double size = 4;
        double sizeRan = 0;
        double rotation = 0;
        double rotationRan = 0;
        double rotationSpeed = 0;
        double rotationSpeedRan = 0;
        double opacity = 100;
        double opacityRan = 0;
        Color particleColor = Color.White;

        // Physics Prefs
        double gravity = 0;

        public ControlForm()
        {
            InitializeComponent();

            preset = new PSPreset();

            particleTypeComboBox.SelectedIndex = particleType;
            colorPictureBox.BackColor = particleColor;

            particleScene = new ParticleScene(new Size(640, 480), this);
            particleScene.Run(true);
            particleScene.bgColor = Color.FromArgb(10, 10, 20);
            bgColorPictureBox.BackColor = particleScene.bgColor;

            updateControlsFromPreset();
        }

        private void applyButton_Click(object sender, EventArgs e)
        {
            applyPreferencesToParticleSystem();
        }

        public void applyPreferencesToParticleSystem()
        {
            applyPreferencesToPreset();
            applyPresetToParticleSystem();
        }

        private void applyPreferencesToPreset()
        {
            preset.linearEmittInterpolation = linearEmittIntCheckBox.Checked;
            preset.amountPerSec = amountPerSec;
            preset.velocity = velocity;
            preset.velocityRan = velocityRan;
            preset.direction = direction;
            preset.directionRan = directionRan;
            preset.particleType = particleType;
            preset.particleImgPath = particleImgPath;
            preset.liveTime = liveTime;
            preset.liveTimeRan = liveTimeRan;
            preset.size = size;
            preset.sizeRan = sizeRan;
            preset.rotation = rotation;
            preset.rotationRan = rotationRan;
            preset.rotationSpeed = rotationSpeed;
            preset.rotationSpeedRan = rotationSpeedRan;
            preset.opacity = opacity;
            preset.opacityRan = opacityRan;
            preset.color = particleColor;
            preset.gravity = gravity;
        }

        private void applyPresetToParticleSystem()
        {
            particleScene.pSystem.loadPreset(preset);

            if (!"".Equals(particleImgPath) && particleType == ParticleSystem.PARTICLE_IMAGE && particleScene.pSystem.GetParticleImage() == null)
            {
                SendKeys.Send("%");
                MessageBox.Show("Couldn't load image!", "Warning!", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                clearImage();
                Refresh();
            }
        }

        private void quitButton_Click(object sender, EventArgs e)
        {
            Close();
        }

        private void loadImageButton_Click(object sender, EventArgs e)
        {
            particleScene.particleForm.Hide();

            OpenFileDialog openFileDialog = new OpenFileDialog();
            openFileDialog.Title = "Load an image";
            openFileDialog.InitialDirectory = Application.StartupPath;
            openFileDialog.Multiselect = false;
            openFileDialog.Filter = "Bilddaten|*.png;*.jpg;*.jpeg;*.gif";

            if (openFileDialog.ShowDialog() == DialogResult.OK)
            {
                string absolutePath = openFileDialog.FileName;
                string relativePath = absolutePath.Replace(Application.StartupPath + "\\", "");

                if (openFileDialog.FileName.Equals(Application.StartupPath + "\\" + relativePath))
                {
                    particleImgPath = relativePath;
                    imageFileLabel.Text = Path.GetFileName(openFileDialog.FileName);
                    Refresh();
                }
                else
                {
                    MessageBox.Show("The image file needs to be copied into the application's startup directory or one of its subdirectories!", "Warning!", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                }
            }

            particleScene.particleForm.Show();
        }

        private void clearImgButton_Click(object sender, EventArgs e)
        {
            clearImage();
        }

        private void clearImage()
        {
            particleImgPath = "";
            imageFileLabel.Text = "No image loaded...";
        }

        private void particleTypeComboBox_SelectedIndexChanged(object sender, EventArgs e)
        {
            particleType = particleTypeComboBox.SelectedIndex;

            if (particleType == ParticleSystem.PARTICLE_IMAGE)
                setNonImageControls(false);
            else
                setNonImageControls(true);
        }

        private void setNonImageControls(bool value)
        {
            sizeTextBox.Enabled = value;
            sizeTrackBar.Enabled = value;
            sizeRanTextBox.Enabled = value;
            colorPictureBox.Enabled = value;
        }

        private void amountTrackBar_Scroll(object sender, EventArgs e)
        {
            processTrackBarScroll(ref amountPerSec, amountTrackBar, amountTextBox);
        }

        private void velocityTrackBar_Scroll(object sender, EventArgs e)
        {
            processTrackBarScroll(ref velocity, velocityTrackBar, velocityTextBox);
        }

        private void directionTrackBar_Scroll(object sender, EventArgs e)
        {
            processTrackBarScroll(ref direction, directionTrackBar, directionTextBox);
        }

        private void liveTimeTrackBar_Scroll(object sender, EventArgs e)
        {
            processTrackBarScroll(ref liveTime, liveTimeTrackBar, liveTimeTextBox, 1);
        }

        private void sizeTrackBar_Scroll(object sender, EventArgs e)
        {
            processTrackBarScroll(ref size, sizeTrackBar, sizeTextBox);
        }

        private void rotationTrackBar_Scroll(object sender, EventArgs e)
        {
            processTrackBarScroll(ref rotation, rotationTrackBar, rotationTextBox);
        }

        private void rotationSpeedTrackBar_Scroll(object sender, EventArgs e)
        {
            processTrackBarScroll(ref rotationSpeed, rotationSpeedTrackBar, rotationSpeedTextBox);
        }

        private void opacityTrackBar_Scroll(object sender, EventArgs e)
        {
            processTrackBarScroll(ref opacity, opacityTrackBar, opacityTextBox);
        }

        private void gravityTrackBar_Scroll(object sender, EventArgs e)
        {
            processTrackBarScroll(ref gravity, gravityTrackBar, gravityTextBox);
        }

        private void processTrackBarScroll(ref double param, TrackBar trackBar, TextBox textBox)
        {
            processTrackBarScroll(ref param, trackBar, textBox, 0);
        }

        private void processTrackBarScroll(ref double param, TrackBar trackBar, TextBox textBox, int offset)
        {
            double trackbarValue = trackBar.Value;
            for (int i = 0; i < offset; i++)
            {
                trackbarValue /= 10;
            }

            param = trackbarValue;
            textBox.Text = "" + param;
        }

        private void amountTextBox_TextChanged(object sender, EventArgs e)
        {
            processParamTextChanged(ref amountPerSec, amountTextBox, amountTrackBar, false);
        }

        private void velocityTextBox_TextChanged(object sender, EventArgs e)
        {
            processParamTextChanged(ref velocity, velocityTextBox, velocityTrackBar, true);
        }

        private void directionTextBox_TextChanged(object sender, EventArgs e)
        {
            processParamTextChanged(ref direction, directionTextBox, directionTrackBar, true);
        }

        private void liveTimeTextBox_TextChanged(object sender, EventArgs e)
        {
            processParamTextChanged(ref liveTime, liveTimeTextBox, liveTimeTrackBar, false, 1);
        }

        private void sizeTextBox_TextChanged(object sender, EventArgs e)
        {
            processParamTextChanged(ref size, sizeTextBox, sizeTrackBar, false);
        }

        private void rotationTextBox_TextChanged(object sender, EventArgs e)
        {
            processParamTextChanged(ref rotation, rotationTextBox, rotationTrackBar, true);
        }

        private void rotationSpeedTextBox_TextChanged(object sender, EventArgs e)
        {
            processParamTextChanged(ref rotationSpeed, rotationSpeedTextBox, rotationSpeedTrackBar, true);
        }

        private void opacityTextBox_TextChanged(object sender, EventArgs e)
        {
            processParamTextChanged(ref opacity, opacityTextBox, opacityTrackBar, false);
        }

        private void gravityTextBox_TextChanged(object sender, EventArgs e)
        {
            processParamTextChanged(ref gravity, gravityTextBox, gravityTrackBar, true);
        }

        private void processParamTextChanged(ref double param, TextBox textBox, TrackBar trackBar, bool allowNegValue)
        {
            processParamTextChanged(ref param, textBox, trackBar, allowNegValue, 0);
        }

        private void processParamTextChanged(ref double param, TextBox textBox, TrackBar trackBar, bool allowNegValue, int trackbarOffset)
        {
            try
            {
                param = double.Parse(textBox.Text);
                
                double trackBarValue = param;

                for (int i = 0; i < trackbarOffset; i++)
                {
                    trackBarValue *= 10;
                }

                if (param < 0)
                {
                    if (!allowNegValue)
                    {
                        param = 0;
                        textBox.Text = "" + param;
                    }
                    trackBarValue = 0;
                }

                if (trackBarValue > trackBar.Maximum) trackBarValue = trackBar.Maximum;
                trackBar.Value = (int)trackBarValue;
            }
            catch (FormatException)
            {
                string inputPattern = "^\\d*,?\\d*$";
                if (allowNegValue) inputPattern = "^-?\\d*,?\\d*$";

                if (!Regex.IsMatch(textBox.Text, inputPattern)) textBox.Text = "" + param;
            }
        }
        
        private void amountTextBox_Leave(object sender, EventArgs e)
        {
            checkParamTextOnLeave(amountTextBox, ref amountPerSec);
        }

        private void velocityTextBox_Leave(object sender, EventArgs e)
        {
            checkParamTextOnLeave(velocityTextBox, ref velocity);
        }

        private void directionTextBox_Leave(object sender, EventArgs e)
        {
            checkParamTextOnLeave(directionTextBox, ref direction);
        }

        private void liveTimeTextBox_Leave(object sender, EventArgs e)
        {
            checkParamTextOnLeave(liveTimeTextBox, ref liveTime);
        }

        private void sizeTextBox_Leave(object sender, EventArgs e)
        {
            checkParamTextOnLeave(sizeTextBox, ref size);
        }

        private void rotationTextBox_Leave(object sender, EventArgs e)
        {
            checkParamTextOnLeave(rotationTextBox, ref rotation);
        }

        private void rotationSpeedTextBox_Leave(object sender, EventArgs e)
        {
            checkParamTextOnLeave(rotationSpeedTextBox, ref rotationSpeed);
        }

        private void opacityTextBox_Leave(object sender, EventArgs e)
        {
            checkParamTextOnLeave(opacityTextBox, ref opacity);
        }

        private void gravityTextBox_Leave(object sender, EventArgs e)
        {
            checkParamTextOnLeave(gravityTextBox, ref gravity);
        }

        private void checkParamTextOnLeave(TextBox textBox, ref double param)
        {
            textBox.Text = "" + param;
        }

        private void saveButton_Click(object sender, EventArgs e)
        {
            particleScene.particleForm.Hide();

            SaveFileDialog saveFileDialog = new SaveFileDialog();
            saveFileDialog.Filter = "XML|*.xml";
            saveFileDialog.Title = "Save preset";
            saveFileDialog.InitialDirectory = Application.StartupPath;
            saveFileDialog.ShowDialog();
            
            if (saveFileDialog.FileName != "")
            {
                applyPreferencesToPreset();
                XDocument document = preset.exportXML();

                addCreatorPrefsToXml(document);

                document.Save(saveFileDialog.FileName);
                
            }

            particleScene.particleForm.Show();
        }

        private void addCreatorPrefsToXml(XDocument document)
        {
            XElement creatorElem = new XElement("Creator");

            XElement bgColorElem = new XElement("BGColor");
            bgColorElem.Value = particleScene.bgColor.R + "," + particleScene.bgColor.G + "," + particleScene.bgColor.B;

            creatorElem.Add(bgColorElem);

            document.Root.Add(creatorElem);
        }

        private void loadButton_Click(object sender, EventArgs e)
        {
            particleScene.particleForm.Hide();

            OpenFileDialog openFileDialog = new OpenFileDialog();
            openFileDialog.Title = "Load a preset";
            openFileDialog.InitialDirectory = Application.StartupPath;
            openFileDialog.Multiselect = false;
            openFileDialog.Filter = "XML|*.xml";

            if (openFileDialog.ShowDialog() == DialogResult.OK)
            {
                XDocument document = null;
                try
                {
                    document = XDocument.Load(openFileDialog.FileName);
                }
                catch (XmlException)
                {
                    MessageBox.Show("Couldn't open XML file!", "Warning!", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                }

                if (document != null)
                {
                    loadCreatorPrefs(document);
                    preset.importXML(document);
                    updateControlsFromPreset();
                    applyPresetToParticleSystem();
                }
            }

            particleScene.particleForm.Show();
        }

        private void loadCreatorPrefs(XDocument document)
        {
            foreach (XNode node in document.Root.Nodes())
            {
                if (node is XElement)
                {
                    XElement element = (XElement)node;

                    // Import Creator Prefs...
                    if ("Creator".Equals(element.Name.LocalName))
                    {
                        foreach (XNode creatorNode in element.Nodes())
                        {
                            if (creatorNode is XElement)
                            {
                                XElement creatorElement = (XElement)creatorNode;

                                if ("BGColor".Equals(creatorElement.Name.LocalName))
                                {
                                    string[] channelValues = creatorElement.Value.Split(',');
                                    int r = GetIntFromString(channelValues[0], 0);
                                    int g = GetIntFromString(channelValues[1], 0);
                                    int b = GetIntFromString(channelValues[2], 0);

                                    particleScene.bgColor = Color.FromArgb(r, g, b);
                                    bgColorPictureBox.BackColor = particleScene.bgColor;
                                }
                            }
                        }
                    }
                }
            }
        }

        private int GetIntFromString(string value, int defaultValue)
        {
            try
            {
                return int.Parse(value);
            }
            catch (FormatException)
            {
                return defaultValue;
            }
        }

        private void updateControlsFromPreset()
        {
            // Emitter Prefs
            linearEmittIntCheckBox.Checked = preset.linearEmittInterpolation;
            amountTextBox.Text = "" + preset.amountPerSec;
            velocityTextBox.Text = "" + preset.velocity;
            velocityRanTextBox.Text = "" + preset.velocityRan;
            directionTextBox.Text = "" + preset.direction;
            directionRanTextBox.Text = "" + preset.directionRan;
            
            // Particle Prefs
            particleTypeComboBox.SelectedIndex = preset.particleType;
            particleImgPath = preset.particleImgPath;
            string imageFileName = Path.GetFileName(particleImgPath);
            if ("".Equals(imageFileName)) imageFileName = "No image loaded...";
            imageFileLabel.Text = imageFileName;
            liveTimeTextBox.Text = "" + preset.liveTime;
            lifeRanTimeTextBox.Text = "" + preset.liveTimeRan;
            sizeTextBox.Text = "" + preset.size;
            sizeRanTextBox.Text = "" + preset.sizeRan;
            rotationTextBox.Text = "" + preset.rotation;
            rotationRanTextBox.Text = "" + preset.rotationRan;
            rotationSpeedTextBox.Text = "" + preset.rotationSpeed;
            rotationSpeedRanTextBox.Text = "" + preset.rotationSpeedRan;
            opacityTextBox.Text = "" + preset.opacity;
            opacityRanTextBox.Text = "" + preset.opacityRan;
            particleColor = preset.color;
            colorPictureBox.BackColor = particleColor;

            // Physics Prefs
            gravityTextBox.Text = "" + preset.gravity;
            
            Refresh();
        }

        private void bgColorPictureBox_MouseClick(object sender, MouseEventArgs e)
        {
            particleScene.particleForm.Hide();

            ColorDialog colorDialog = new ColorDialog();
            colorDialog.Color = particleScene.bgColor;

            if (colorDialog.ShowDialog() == DialogResult.OK)
                particleScene.bgColor = colorDialog.Color;

            bgColorPictureBox.BackColor = particleScene.bgColor;
            bgColorPictureBox.Refresh();

            particleScene.particleForm.Show();
        }

        private void colorPictureBox_Click(object sender, EventArgs e)
        {
            particleScene.particleForm.Hide();

            ColorDialog colorDialog = new ColorDialog();
            colorDialog.Color = particleColor;

            if (colorDialog.ShowDialog() == DialogResult.OK)
                particleColor = colorDialog.Color;

            colorPictureBox.BackColor = particleColor;
            colorPictureBox.Refresh();
            Refresh();

            particleScene.particleForm.Show();
        }

        private void ranVelocityTextBox_TextChanged(object sender, EventArgs e)
        {
            processRandomTextChanged(ref velocityRan, velocityRanTextBox);
        }

        private void ranDirectionTextBox_TextChanged(object sender, EventArgs e)
        {
            processRandomTextChanged(ref directionRan, directionRanTextBox);
        }

        private void ranLifeTimeTextBox_TextChanged(object sender, EventArgs e)
        {
            processRandomTextChanged(ref liveTimeRan, lifeRanTimeTextBox);
        }

        private void ranSizeTextBox_TextChanged(object sender, EventArgs e)
        {
            processRandomTextChanged(ref sizeRan, sizeRanTextBox);
        }

        private void ranRotationTextBox_TextChanged(object sender, EventArgs e)
        {
            processRandomTextChanged(ref rotationRan, rotationRanTextBox);
        }

        private void rotationSpeedRanTextBox_TextChanged(object sender, EventArgs e)
        {
            processRandomTextChanged(ref rotationSpeedRan, rotationSpeedRanTextBox);
        }

        private void opacityRanTextBox_TextChanged(object sender, EventArgs e)
        {
            processRandomTextChanged(ref opacityRan, opacityRanTextBox);
        }

        private void processRandomTextChanged(ref double param, TextBox textBox)
        {
            if ("".Equals(textBox.Text)) return;

            try
            {
                param = double.Parse(textBox.Text);

                if (param < 0) param = 0;
                else if (param > 100) param = 100;

                textBox.Text = "" + param;
            }
            catch (FormatException)
            {
                textBox.Text = "" + param;
            }
        }

        private void ranVelocityTextBox_Leave(object sender, EventArgs e)
        {
            checkParamTextOnLeave(velocityRanTextBox, ref velocityRan);
        }

        private void ranDirectionTextBox_Leave(object sender, EventArgs e)
        {
            checkParamTextOnLeave(directionRanTextBox, ref directionRan);
        }

        private void ranLifeTimeTextBox_Leave(object sender, EventArgs e)
        {
            checkParamTextOnLeave(lifeRanTimeTextBox, ref liveTimeRan);
        }

        private void ranSizeTextBox_Leave(object sender, EventArgs e)
        {
            checkParamTextOnLeave(sizeRanTextBox, ref sizeRan);
        }

        private void ranRotationTextBox_Leave(object sender, EventArgs e)
        {
            checkParamTextOnLeave(rotationRanTextBox, ref rotationRan);
        }

        private void rotationSpeedRanTextBox_Leave(object sender, EventArgs e)
        {
            checkParamTextOnLeave(rotationSpeedRanTextBox, ref rotationSpeedRan);
        }

        private void opacityRanTextBox_Leave(object sender, EventArgs e)
        {
            checkParamTextOnLeave(opacityRanTextBox, ref opacityRan);
        }
    }
}
